#include <iostream>
#include <filesystem>
#include <cmath>
#include <locale>
#include "tianditu_select.h"

#define M_PI 3.14159265358979323846

extern std::wstring JSONResourceData;

TianDiTuSelector::TianDiTuSelector(const std::string& openDir, const std::string& saveDir, int level, TileMode tileMode)
    : _openDir(openDir)
    , _saveDir(saveDir)
    , _level(level)
    , _tileMode(tileMode)
    , _extent({0, 0, 0, 0})
{
    std::cout << "Initializing..." << std::endl;
    nlohmann::json solver;
    solver = nlohmann::json::parse(JSONResourceData);
    FromJson(solver, _dataSource);
    std::cout << "Initialization success" << std::endl;
}

void TianDiTuSelector::move(const std::string& regName)
{
    _extent = TianDiTuSelector::getExtent(regName);
    std::cout << "Extent: " << _extent.minLng << ", " << _extent.maxLng << ", " << _extent.minLat << ", "
              << _extent.maxLat << std::endl;
    if (_extent.minLng == 0 && _extent.maxLng == 0 && _extent.minLat == 0 && _extent.maxLat == 0)
    {
        return;
    }
    Lnglat minLnglat = {_extent.minLng, _extent.minLat};
    Lnglat maxLnglat = {_extent.maxLng, _extent.maxLat};
    TileIndex minXY = TianDiTuSelector::lnglatToTileIndex(minLnglat);
    TileIndex maxXY = TianDiTuSelector::lnglatToTileIndex(maxLnglat);
    int tileNum = (maxXY.x - minXY.x + 1) * (maxXY.y - minXY.y + 1);
    std::cout << "Total " << tileNum << " tiles" << std::endl;
    for (int i = 0; i < tileNum; ++i)
    {
        TileIndex xy = {minXY.x + (i % (maxXY.x - minXY.x + 1)), minXY.y + (i / (maxXY.x - minXY.x + 1))};
        std::string savePath = TianDiTuSelector::moveTile(xy);
        if (savePath == "")
        {
            std::cout << "[" << i + 1 << "/" << tileNum << "] Tile cannot be saved" << std::endl;
        }
        else
        {
            std::cout << "[" << i + 1 << "/" << tileNum << "] Tile (" << savePath << ") saved" << std::endl;
        }
    }
}

Extent TianDiTuSelector::getExtent(const std::string& regName)
{
    std::cout << "Get extent of " << regName << std::endl;
    Extent extent = {0, 0, 0, 0};
    findExtent(_dataSource, regName, extent);
    return extent;
}

TileIndex TianDiTuSelector::lnglatToTileIndex(Lnglat lnglat)
{
    double x = (lnglat.lng + 180) / 360;
    double y = (1 - std::log(std::tan(lnglat.lat * M_PI / 180) + 1 / std::cos(lnglat.lat * M_PI / 180)) / M_PI) / 2;
    double titleX = std::floor(x * pow(2, _level));
    double titleY = std::floor(y * pow(2, _level));
    return TileIndex{static_cast<int>(titleX), static_cast<int>(titleY)};
}

std::string TianDiTuSelector::moveTile(TileIndex xy)
{
    std::string tilePath = std::to_string(_level) + "/";
    if (_tileMode == TileMode::ZXY)
    {
        tilePath += std::to_string(xy.x) + "/" + std::to_string(xy.y);
    }
    else
    {
        tilePath += std::to_string(xy.y) + "/" + std::to_string(xy.x);
    }
    tilePath += "/tile.png";
    std::string openPath = _openDir + "/" + tilePath;
    std::string savePath = _saveDir + "/" + tilePath;
    if (!std::filesystem::exists(std::filesystem::path(openPath)))
    {
        return "";
    }
    std::filesystem::create_directories(std::filesystem::path(savePath).parent_path());
    std::filesystem::copy(openPath, savePath);
    return savePath;
}